#! /usr/bin/env python
# -----------------------------------------------------------------------------
# Copyright (c) 2014-2016, PyInstaller Development Team.
#
# Distributed under the terms of the GNU General Public License with exception
# for distributing bootloader.
#
# The full license is in the file COPYING.txt, distributed with this software.
# -----------------------------------------------------------------------------


"""
Bootloader building script.
"""

import os
import platform
import sys
import sysconfig
from waflib.Configure import conf
from waflib import Logs

# The following two variables are used by the target "waf dist"
VERSION = ''
APPNAME = ''

# These variables are mandatory ('/' are converted automatically)
top = '.'
out = 'build'

# Platform specific switches.
is_win = platform.system() == 'Windows'
is_darwin = platform.system() == 'Darwin'
is_linux = platform.system() == 'Linux'
is_freebsd = sys.platform.startswith('freebsd')
# TODO find out proper value for AIX/Solaris
is_solar = platform.system() in ['Solaris', 'SunOS']
is_aix = platform.system() == 'AIX'
is_hpux = platform.system() == 'HP-UX'

# Build variants of bootloader.
# PyInstaller provides debug/release bootloaders and console/windowed
# variants.
# Every variant has different exe name.
variants = {
    'debug': 'run_d',
    'debugw': 'runw_d',
    'release': 'run',
    'releasew': 'runw',
}


if is_darwin:
    # OS X 10.7 might not understand some load commands.
    # The following variable fixes 10.7 compatibility.
    # According to OS X doc this variable is equivalent to gcc option:
    #   -mmacosx-version-min=10.7
    os.environ['MACOSX_DEPLOYMENT_TARGET'] = '10.7'


# TODO strip created binaries.


def architecture():
    """
    on 64bit Mac function platform.architecture() returns 64bit even
    for 32bit Python. This is the workaround for this.
    """

    if is_darwin and sys.maxsize <= 3 ** 32:

        return '32bit'
    else:
        return platform.architecture()[0]  # 32bit or 64bit


def machine():
    """
    Differenciate path to bootloader with machine name if necessary.

    Machine name in bootloader is necessary only for non-x86 architecture.
    """
    mach = None  # Assume x86/x86_64 machine.
    # Different name for arm is necessary.
    if platform.machine().startswith('arm'):
        mach = 'arm'
    return mach


def options(ctx):
    ctx.load('compiler_c')

    ctx.add_option('--debug',
                   action='store_true',
                   help='Include debugging info for GDB.',
                   default=False,
                   dest='debug')
    ctx.add_option('--leak-detector',
                   action='store_true',
                   help='Link with Boehm garbage collector to detect memory leaks.',
                   default=False,
                   dest='boehmgc')
    ctx.add_option('--clang',
                   action='store_true',
                   help='Try to find clang C compiler instead of gcc.',
                   default=False,
                   dest='clang')
    ctx.add_option('--gcc',
                   action='store_true',
                   help='Try to find GNU C compiler.',
                   default=False,
                   dest='gcc')
    ctx.add_option('--target-arch',
                   action='store',
                   help='Target architecture format (32bit, 64bit). '
                        'This option allows to build 32bit bootloader with 64bit compiler '
                        'and 64bit Python.',
                   default=None,
                   dest='target_arch')
    ctx.add_option('--target-cpu',
                   action='store',
                   help='Target CPU format (x86, amd64).',
                   default=None,
                   dest='target_cpu')

    grp = ctx.add_option_group('Linux Standard Base (LSB)',
                               'These options have effect only on Linux.')
    grp.add_option('--no-lsb',
                   action='store_true',
                   help='Prevent building LSB (Linux Standard Base) bootloader.',
                   default=False,
                   dest='nolsb')
    grp.add_option('--lsbcc-path',
                   action='store',
                   help='Path where to look for lsbcc. By default PATH is '
                        'searched for lsbcc otherwise is tried file '
                        '/opt/lsb/bin/lsbcc. [Default: lsbcc]',
                   default=None,
                   dest='lsbcc_path')
    grp.add_option('--lsb-target-version',
                   action='store',
                   help='Specify LSB target version [Default: 4.0]',
                   default='4.0',
                   dest='lsb_version')


@conf
def set_lsb_compiler(ctx):
    """
    Build LSB (Linux Standard Base) bootloader.

    LSB bootloader allows to build bootloader binary that is compatible
    with almost every Linux distribution.
    'lsbcc' just wraps gcc in a special way.
    """
    Logs.pprint('CYAN', 'Building LSB (Linux Standard Base) bootloader.')
    lsb_paths = ['/opt/lsb/bin']
    if ctx.options.lsbcc_path:
        lsb_paths.insert(0, ctx.options.lsbcc_path)
    try:
        ctx.find_program('lsbcc', var='LSBCC', path_list=lsb_paths)
    except ctx.errors.ConfigurationError:
        # Fail hard and print warning if lsbcc is not available.
        # if not ctx.env.LSBCC:
        ctx.fatal('LSB (Linux Standard Base) tools >= 4.0 are '
                  'required.\nTry --no-lsb option if not interested in '
                  'building LSB binary.')

    # lsbcc as CC compiler
    ctx.env.append_value('CFLAGS', '--lsb-cc=%s' % ctx.env.CC[0])
    ctx.env.append_value('LINKFLAGS', '--lsb-cc=%s' % ctx.env.CC[0])
    ctx.env.CC = ctx.env.LSBCC
    ctx.env.LINK_CC = ctx.env.LSBCC
    ## check LSBCC flags
    # --lsb-besteffort - binary will work on platforms without LSB stuff
    # --lsb-besteffort - available in LSB build tools >= 4.0
    ctx.check_cc(ccflags='--lsb-besteffort',
                 msg='Checking for LSB build tools >= 4.0',
                 errmsg='LSB >= 4.0 is required', mandatory=True)
    ctx.env.append_value('CFLAGS', '--lsb-besteffort')
    ctx.env.append_value('LINKFLAGS', '--lsb-besteffort')
    # binary compatibility with a specific LSB version
    # LSB 4.0 can generate binaries compatible with 3.0, 3.1, 3.2, 4.0
    # however because of using function 'mkdtemp', loader requires
    # using target version 4.0
    lsb_target_flag = '--lsb-target-version=%s' % ctx.options.lsb_version
    ctx.env.append_value('CFLAGS', lsb_target_flag)
    ctx.env.append_value('LINKFLAGS', lsb_target_flag)


@conf
def detect_arch_cpu(ctx):
    """
    Handle options --target-arch and --target-cpu or use the same
    architecture as the Python interpreter.
    """
    # Get arch/cpu values either from CLI or detect it.
    arch = ctx.options.target_arch or architecture()
    # 'target_cpu is useful only on Windows for now.
    cpu = ctx.options.target_cpu or ''  # Use empty string if not specified.

    # Print message based on arch/cpu.
    if ctx.options.target_arch or ctx.options.target_cpu:
        if cpu == 'amd64':
            ctx.env['MSVC_TARGETS'] = ['x64']
            arch = '64bit'  # Force 64bit for amd64 cpu.
        elif cpu == 'x86':
            ctx.env['MSVC_TARGETS'] = ['x86']
            arch = '32bit'  # Force 32bit for x86 cpu.
            ctx.msg('Platform', 'Architecture manually chosen: x86')
        ctx.msg('Platform', '%s-%s %s  manually chosen' % (platform.system(), arch, cpu))
    else:
        ctx.msg('Platform', '%s-%s detected' % (platform.system(), arch))

    if is_darwin and arch == '64bit':
        ctx.msg('CYAN', 'WARNING: Building bootloader for Python 64-bit on Mac OSX')
        ctx.msg('CYAN', 'For 32b-bit bootloader prepend the python command with:')
        ctx.msg('CYAN', 'VERSIONER_PYTHON_PREFER_32_BIT=yes arch -i386 python')

    # Pass return values as environment variables.
    ctx.env.PYI_ARCH = arch  # '32bit' or '64bit'
    ctx.env.PYI_CPU = cpu  # 'x86' or 'amd64' or empty string

    if arch == '64bit':
        ctx.env['MSVC_TARGETS'] = ['x64']
    elif arch == '32bit':
        ctx.env['MSVC_TARGETS'] = ['x86']


@conf
def set_arch_flags(ctx):
    """
    Set properly architecture flag (32 or 64 bit) cflags for compiler
    and CPU target for compiler.
    """
    if is_win and ctx.env.CC_NAME == 'msvc':
        # Set msvc linkflags based on --target-cpu.
        if ctx.env.PYI_CPU:
            if ctx.options.target_cpu == 'x86':
                ctx.env.append_value('LINKFLAGS', '/MACHINE:X86')
                # Set LARGE_ADDRESS_AWARE_FLAG to True.
                # On Windows this allows 32bit apps to use 4GB of memory and
                ctx.env.append_value('LINKFLAGS', '/LARGEADDRESSAWARE')
            elif ctx.options.target_cpu == 'amd64':
                ctx.env.append_value('LINKFLAGS', '/MACHINE:X64')
            else:
                ctx.fatal('Unrecognized target CPU: %s' % ctx.env.PYI_CPU)
        # Set msvc linkflags based on architecture.
        else:
            if ctx.env.PYI_ARCH == '32bit':
                ctx.env.append_value('LINKFLAGS', '/MACHINE:X86')
                # Set LARGE_ADDRESS_AWARE_FLAG to True.
                # On Windows this allows 32bit apps to use 4GB of memory and
                ctx.env.append_value('LINKFLAGS', '/LARGEADDRESSAWARE')

            elif ctx.env.PYI_ARCH == '64bit':
                ctx.env.append_value('LINKFLAGS', '/MACHINE:X64')

        # Enable 64bit porting warnings and other warnings too.
        ctx.env.append_value('CFLAGS', '/W3')
        # We use SEH exceptions in winmain.c; make sure they are activated.
        ctx.env.append_value('CFLAGS', '/EHa')

    # Ensure proper architecture flags on Mac OS X.
    elif is_darwin:
        # Default compiler on Mac OS X is Clang.
        # Clang does not have flags '-m32' and '-m64'.
        if ctx.env.PYI_ARCH == '32bit':
            mac_arch = ['-arch', 'i386']
        else:
            mac_arch = ['-arch', 'x86_64']
        ctx.env.append_value('CFLAGS', mac_arch)
        ctx.env.append_value('CXXFLAGS', mac_arch)
        ctx.env.append_value('LINKFLAGS', mac_arch)

    # AIX specific flags
    elif is_aix:
        if ctx.env.CC_NAME == 'gcc':
            ctx.check_cc(ccflags='-maix32', msg='Checking for flags -maix32')
            ctx.env.append_value('CFLAGS', '-maix32')
            ctx.env.append_value('LINKFLAGS', '-maix32')
        else:
            # We use xlc compiler
            pass

    elif is_hpux:
        if ctx.env.CC_NAME == 'gcc':
            if ctx.env.PYI_ARCH == '32bit':
                ctx.check_cc(ccflags='-milp32', msg='Checking for flags -milp32')
                ctx.env.append_value('CFLAGS', '-milp32')
                ctx.env.append_value('LINKFLAGS', '-milp32')
            else:
                ctx.check_cc(ccflags='-mlp64', msg='Checking for flags -mlp64')
                ctx.env.append_value('CFLAGS', '-mlp64')
                ctx.env.append_value('LINKFLAGS', '-mlp64')
        else:
            # We use xlc compiler
            pass


    # Other compiler - not msvc.
    else:
        if machine() == 'sw_64':
            # The gcc has no '-m64' option under sw64 machine, but the
            # __x86_64__ macro needs to be defined
            conf.env.append_value('CCDEFINES', '__x86_64__')
        # This ensures proper compilation with 64bit gcc and 32bit Python
        # or vice versa or with manually choosen --target-arch.
        # Option -m32/-m64 has to be passed to cflags and linkflages.
        elif ctx.env.PYI_ARCH == '32bit':
            # It was reported that flag '-m32' does not work with gcc
            # on 32-bit arm Linux. So skip the -m32 flag.
            if not (machine() == 'arm' and is_linux):
                ctx.check_cc(ccflags='-m32', msg='Checking for flags -m32')
                ctx.env.append_value('CFLAGS', '-m32')
                ctx.env.append_value('LINKFLAGS', '-m32')
            # Set LARGE_ADDRESS_AWARE_FLAG to True.
            # On Windows this allows 32bit apps to use 4GB of memory and
            # not only 2GB.
            # TODO verify if this option being as default might cause any side effects.
            if is_win:
                ctx.env.append_value('LINKFLAGS', '-Wl,--large-address-aware')
        elif ctx.env.PYI_ARCH == '64bit':
            ctx.check_cc(ccflags='-m64', msg='Checking for flags -m64')
            ctx.env.append_value('CFLAGS', '-m64')
            ctx.env.append_value('LINKFLAGS', '-m64')
        else:
            ctx.fatal('Unrecognized target architecture: %s' % ctx.env.PYI_ARCH)

    # We need to pass architecture switch to the 'windres' tool.
    if is_win and ctx.env.CC_NAME != 'msvc':
        if ctx.env.PYI_ARCH == '32bit':
            ctx.env.WINRCFLAGS = ['--target=pe-i386']
        else:
            ctx.env.WINRCFLAGS = ['--target=pe-x86-64']


def configure(ctx):
    # Detect architecture and CPU
    ctx.detect_arch_cpu()

    ### C compiler

    # Allow to use Clang if preferred.
    if ctx.options.clang:
        ctx.load('clang')
    # Allow to use gcc if preferred.
    elif ctx.options.gcc:
        ctx.load('gcc')
    else:
        ctx.load('compiler_c')  # Any available C compiler.

    # LSB compatible bootloader only for Linux and without cli option --no-lsb.
    if is_linux and not ctx.options.nolsb:
        ctx.set_lsb_compiler()

    if is_win:
        # Do not embed manifest file when using MSVC (Visual Studio).
        # Manifest file will be added in the phase of packaging python
        # application by PyInstaller.
        ctx.env.MSVC_MANIFEST = False
        # Load tool to process *.rc* files for C/C++ like icon for exe files.
        ctx.load('winres')

    # Set proper architecture and CPU for C compiler
    ctx.set_arch_flags()

    # TODO Set proper optimization flags for MSVC (Visual Studio).
    ### C Compiler optimizations.

    if ctx.options.debug:
        if is_win and ctx.env.CC_NAME == 'msvc':
            # Include information for debugging in MSVC/msdebug
            ctx.env.append_value('CFLAGS', '/Z7')
            ctx.env.append_value('CFLAGS', '/Od')
            ctx.env.append_value('LINKFLAGS', '/DEBUG')
        else:
            # Include gcc debugging information for debugging in GDB.
            ctx.env.append_value('CFLAGS', '-g')
    else:
        ctx.env.append_value('CFLAGS', '-O2')

    if ctx.env.CC_NAME != 'msvc':
        # This tool allows reduce the size of executables.
        ctx.load('strip', tooldir='tools')

    if ctx.env.CC_NAME == 'gcc':
        # !! These flags are gcc specific

        # Make sure we don't use declarations after statement. It breaks
        # MSVC (Visual Studio).
        ctx.env.append_value('CFLAGS', '-Wdeclaration-after-statement')

        # Require function declarations to avoid data type length mismatches
        # Implicit return value is 'int' which is only 32 bits on Windows.
        ctx.env.append_value('CFLAGS', '-Wimplicit-function-declaration')
        ctx.env.append_value('CFLAGS', '-Werror')

    ### Defines, Includes

    if not is_win:
        # Defines common for Unix and Unix-like platforms.
        # For details see:
        #   http://man.he.net/man7/feature_test_macros
        #
        ## Without these definitions compiling might fail on OSX.
        ctx.env.append_value('DEFINES', '_POSIX_C_SOURCE=200112L')
        # SUS v2 (UNIX 98) definitions.
        #   Mac OS X 10.5 is UNIX 03 compliant.
        ctx.env.append_value('DEFINES', '_XOPEN_SOURCE=600')
        ctx.env.append_value('DEFINES', '_REENTRANT')

        # mkdtemp() is available only if _BSD_SOURCE is defined.
        ctx.env.append_value('DEFINES', '_BSD_SOURCE')

        if is_linux:
            # Recent GCC 5.x complains about _BSD_SOURCE under Linux:
            #     _BSD_SOURCE and _SVID_SOURCE are deprecated, use _DEFAULT_SOURCE
            ctx.env.append_value('DEFINES', '_DEFAULT_SOURCE')

            # TODO What other platforms support _FORTIFY_SOURCE macro? OS X?
            # TODO OS X's CLang appears to support this macro as well. See:
            # http://permalink.gmane.org/gmane.comp.compilers.clang.devel/2442

            # For security, enable the _FORTIFY_SOURCE macro detecting buffer
            # overflows in various string and memory manipulation functions.
            if ctx.env.CC_NAME == 'gcc':
                # Undefine this macro if already defined by default to avoid
                # "macro redefinition" errors.
                ctx.env.append_value('CFLAGS', '-U_FORTIFY_SOURCE')

                # Define this macro.
                ctx.env.append_value('DEFINES', '_FORTIFY_SOURCE=2')
        # On Mac OS X, mkdtemp() is available only with _DARWIN_C_SOURCE.
        elif is_darwin:
            ctx.env.append_value('DEFINES', '_DARWIN_C_SOURCE')

    if is_win:
        ctx.env.append_value('DEFINES', 'WIN32')
        ctx.env.append_value('CPPPATH', '../zlib')

    if is_solar:
        ctx.env.append_value('DEFINES', 'SUNOS')
        if ctx.env.CC_NAME == 'gcc':
            # On Solaris using gcc the linker options for shared and static
            # libraries are slightly different from other platforms.
            ctx.env['SHLIB_MARKER'] = '-Wl,-Bdynamic'
            ctx.env['STLIB_MARKER'] = '-Wl,-Bstatic'
            # On Solaris using gcc, the compiler needs to be gnu99
            ctx.env.append_value('CFLAGS', '-std=gnu99')

    if is_aix:
        ctx.env.append_value('DEFINES', 'AIX')
        # On AIX some APIs are restricted if _ALL_SOURCE is not defined.
        # In the case of PyInstaller, we need the AIX specific flag RTLD_MEMBER
        # for dlopen() which is used to load a shared object from a library
        # archive. We need to load the Python library like this:
        #  dlopen("libpython2.7.a(libpython2.7.so)", RTLD_MEMBER)
        ctx.env.append_value('DEFINES', '_ALL_SOURCE')

        # On AIX using gcc the linker options for shared and static
        # libraries are slightly different from other platforms.
        ctx.env['SHLIB_MARKER'] = '-Wl,-bdynamic'
        ctx.env['STLIB_MARKER'] = '-Wl,-bstatic'

    if is_hpux:
        ctx.env.append_value('DEFINES', 'HPUX')
        if ctx.env.CC_NAME == 'gcc':
            if ctx.env.PYI_ARCH == '32bit':
                ctx.env.append_value('LIBPATH', '/usr/local/lib/hpux32')
                ctx.env.append_value('STATICLIBPATH', '/usr/local/lib/hpux32')
            else:
                ctx.env.append_value('LIBPATH', '/usr/local/lib/hpux64')
                ctx.env.append_value('STATICLIBPATH', '/usr/local/lib/hpux64')

    # Adding python includedir into our environment - does not work on Windows
    if not is_win:
        ctx.env.append_value('INCLUDES', sysconfig.get_config_var('INCLUDEDIR'))

    ### Libraries

    if is_win:
        ctx.check_cc(lib='user32', mandatory=True)
        ctx.check_cc(lib='comctl32', mandatory=True)
        ctx.check_cc(lib='kernel32', mandatory=True)
        ctx.check_cc(lib='ws2_32', mandatory=True)
    else:
        # Mac OS X and FreeBSD do not need libdl.
        # https://stackoverflow.com/questions/20169660/where-is-libdl-so-on-mac-os-x
        if not (is_darwin or is_freebsd):
            ctx.check_cc(lib='dl', mandatory=True)
        # Link to libthr on FreeBSD.
        if is_freebsd and sysconfig.get_config_vars('HAVE_PTHREAD_H').pop():
            ctx.check_cc(lib='thr', mandatory=True)
        elif is_hpux  and sysconfig.get_config_vars('HAVE_PTHREAD_H').pop():
            ctx.check_cc(lib='pthread', mandatory=True)
        ctx.check_cc(lib='m', mandatory=True)
        ctx.check_cc(lib='z', mandatory=True)
        # This uses Boehm GC to manage memory - it replaces malloc() / free()
        # functions. Some messages are printed if memory is not deallocated.
        if ctx.options.boehmgc:
            ctx.check_cc(lib='gc', mandatory=True)
            ctx.env.append_value('DEFINES', 'PYI_LEAK_DETECTOR')
            ctx.env.append_value('DEFINES', 'GC_FIND_LEAK')
            ctx.env.append_value('DEFINES', 'GC_DEBUG')
            ctx.env.append_value('DEFINES', 'SAVE_CALL_CHAIN')

    ### Functions

    # unsetenv not available on AIX, maybe others.
    ctx.check_cc(function_name='unsetenv', header_name="stdlib.h", mandatory=False)
    ctx.check_cc(function_name='mkdtemp', header_name="stdlib.h", mandatory=False)

    ### CFLAGS

    if is_win:
        if ctx.env.CC_NAME == 'msvc':
            # Use Unicode entry point wmain/wWinMain and wchar_t WinAPI
            ctx.env.append_value('CFLAGS', '-DUNICODE')
            ctx.env.append_value('CFLAGS', '-D_UNICODE')
        else:
            # Use Visual C++ compatible alignment
            ctx.env.append_value('CFLAGS', '-mms-bitfields')

            # Define UNICODE and _UNICODE for wchar_t WinAPI
            ctx.env.append_value('CFLAGS', '-municode')

            # Use Unicode entry point wmain/wWinMain
            ctx.env.append_value('LINKFLAGS', '-municode')

    if is_darwin:
        ctx.env.append_value('CFLAGS', '-mmacosx-version-min=10.7')

    # On linux link only with needed libraries.
    # -Wl,--as-needed is on some platforms detected during configure but
    # fails during build. (Mac OS X, Solaris, AIX)
    if is_linux and ctx.check_cc(ccflags='-Wl,--as-needed',
                                 msg='Checking for flags -Wl,--as-needed'):
        ctx.env.append_value('LINKFLAGS', '-Wl,--as-needed')

    ### DEBUG and RELEASE environments
    basic_env = ctx.env

    ## setup DEBUG environment
    ctx.setenv('debug', basic_env)  # Ensure env contains shared values.
    debug_env = ctx.env
    # This define enables verbose console output of the bootloader.
    ctx.env.append_value('DEFINES', ['LAUNCH_DEBUG'])
    ctx.env.append_value('DEFINES', 'NDEBUG')

    ## setup windowed DEBUG environment
    ctx.setenv('debugw', debug_env)  # Ensure env contains shared values.
    ctx.env.append_value('DEFINES', 'WINDOWED')
    # For MinGW disables console window on Windows- MinGW option
    if is_win and not ctx.env.CC_NAME == 'msvc':
        # TODO Is it necessary to have -mwindows for C and LINK flags?
        ctx.env.append_value('LINKFLAGS', '-mwindows')
        ctx.env.append_value('CFLAGS', '-mwindows')
    elif is_darwin:
        # conf.env.append_value('CFLAGS', '-I/Developer/Headers/FlatCarbon')
        # To support catching AppleEvents and running as ordinary OSX GUI app,
        # we have to link against the Carbon framework.
        # This linkage only needs to be there for the windowed bootloaders.
        ctx.env.append_value('LINKFLAGS', '-framework')
        ctx.env.append_value('LINKFLAGS', 'Carbon')
        # conf.env.append_value('LINKFLAGS', '-framework')
        # conf.env.append_value('LINKFLAGS', 'ApplicationServices')

    ## setup RELEASE environment
    ctx.setenv('release', basic_env)  # Ensure env contains shared values.
    release_env = ctx.env
    ctx.env.append_value('DEFINES', 'NDEBUG')

    ## setup windowed RELEASE environment
    ctx.setenv('releasew', release_env)  # Ensure env contains shared values.
    ctx.env.append_value('DEFINES', 'WINDOWED')

    # For MinGW disables console window on Windows- MinGW option
    if is_win and not ctx.env.CC_NAME == 'msvc':
        # TODO Is it necessary to have -mwindows for C and LINK flags?
        ctx.env.append_value('LINKFLAGS', '-mwindows')
        ctx.env.append_value('CFLAGS', '-mwindows')
    elif is_darwin:
        # To support catching AppleEvents and running as ordinary OSX GUI app,
        # we have to link against the Carbon framework.
        # This linkage only needs to be there for the windowed bootloaders.
        ctx.env.append_value('LINKFLAGS', '-framework')
        ctx.env.append_value('LINKFLAGS', 'Carbon')
        # TODO Do we need to link with this framework?
        # conf.env.append_value('LINKFLAGS', '-framework')
        # conf.env.append_value('LINKFLAGS', 'ApplicationServices')


# TODO Use 'strip' command to decrease the size of compiled bootloaders.
def build(ctx):
    if not ctx.variant:
        ctx.fatal('Call "python waf all" to compile all bootloaders.')

    exe_name = variants[ctx.variant]

    install_path = os.path.join(os.getcwd(), '../PyInstaller/bootloader',
                                platform.system() + "-" + ctx.env.PYI_ARCH)
    install_path = os.path.normpath(install_path)

    if machine():
        install_path += '-' + machine()

    if is_win:
        # Do not strip bootloaders when using MSVC.
        if ctx.env.CC_NAME != 'msvc':
            features = 'strip'
        else:
            features = ''
        # Use different RC file (icon) for console/windowed mode - remove '_d'
        icon_rc = 'windows/' + exe_name.replace('_d', '') + '.rc'
        # On Windows we need to link library zlib statically.
        ctx.stlib(
            source=ctx.path.ant_glob('zlib/*.c'),
            target='static_zlib',
            name='zlib',
            includes='zlib',
        )
        ctx.program(
            source=ctx.path.ant_glob('%s src/*.c' % icon_rc),
            target=exe_name,
            install_path=install_path,
            use='USER32 COMCTL32 KERNEL32 WS2_32 zlib',
            includes='src windows zlib',
            # Strip final executables to make them smaller.
            features=features,
        )
    else:
        # Linux, Darwin (MacOSX), ...
        libs = ['DL', 'M', 'Z']  # 'z' - zlib, 'm' - math,
        staticlibs = []
        # Mac OS X and FreeBSD do not need libdl.
        if is_darwin or is_freebsd:
            libs = ['Z', 'M']
            # Check if python has threads: libthr needs
            # to be loaded in the main process
            if is_freebsd and sysconfig.get_config_vars('HAVE_PTHREAD_H').pop():
                libs.append('THR')
        elif is_aix:
            libs = ['dl', 'm']
            staticlibs = ['z']

        if ctx.options.boehmgc:
            libs.append('GC')
        ctx.program(
            source=ctx.path.ant_glob('src/*.c'),
            target=exe_name,
            includes='src',
            use=libs,
            stlib=staticlibs,
            install_path=install_path,
            # Strip final executables to make them smaller.
            features='strip',
        )


def all(ctx):
    """
    Do configure, build, install in one step.
    """
    from waflib import Options
    Options.commands = ['distclean', 'configure', 'build_debug', 'build_release']
    # On Windows and Mac OS X we also need console/windowed bootloaders.
    # On other platforms they make no sense.
    if is_win or is_darwin:
        Options.commands += ['build_debugw', 'build_releasew']
    # Install bootloaders.
    Options.commands += ['install_debug', 'install_release']
    if is_win or is_darwin:
        Options.commands += ['install_debugw', 'install_releasew']


# Set up building several variants of bootloader.
from waflib.Build import BuildContext, InstallContext

for x in variants:
    class BootloaderContext(BuildContext):
        cmd = 'build' + '_' + x
        variant = x


    class BootloaderInstallContext(InstallContext):
        cmd = 'install' + '_' + x
        variant = x
